/*  
  -- The Lime II Project -- 

  A tuplespaces-based middleware for coordinating agents and mobile hosts.
  Copyright (C) 2005.
  Gian Pietro Picco, Amy L. Murphy, Lorenzo Bellini.

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  Lesser General Public License for more details.

  You should have received this copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
*/

package lime2.trackerca;
import lime2.*;
import java.io.*;
import java.net.*;
import java.util.Hashtable;

/**
 * Models a (cache-able) connection.
 * 
 * @author Gian Pietro Picco
 * @author Amy Murphy
 * @author Lorenzo Bellini
 */

class Connection
{
 // this hashtable[LimeServerID,Connection]	caches the instances of Connection
 private static Hashtable cached = new Hashtable(); 	
	
 // privata data members
 private Socket socket;          
 private ObjectOutputStream out; 
 private int error;                

 /**
  * Retrieves (or creates) a new Connection instance.
  *  
  * @param target to connect to
  * @return Connection
  * @throws IOException
  */
 synchronized static Connection get(LimeServerID target) throws IOException
 {
  // don't cache connections
  if (!LimeConstants.CACHE_TCP_CONNECTIONS) return new Connection(target);
  	
  // cache connections 
  Connection ret = (Connection) cached.get(target);
  if (ret != null) // does a cached connection exist ?
  { 
   if (ret.mustReconnect()) // is the cached connection still alive ?
   { 
   	// if the connection is not alive remove it from the hashtable
  	cached.remove(target);
  	// create the new connection: the constructor can throw an IOException ..
  	ret = new Connection(target);
  	// .. thus if a connection cannot be established it will not be cached
   	cached.put(target, ret);
   }	
  }
  else // there is not cached connection: (try to) create a new one
  {
   ret = new Connection(target);
   cached.put(target, ret); 
  }
  return ret; 
 }
  
 /**
  * This method must be invoked when a new connection is accepted.
  * 
  * @param s "accepted" socket
  * @throws IOException
  */
 synchronized static void add(Socket s) throws IOException
 {
  if (LimeConstants.CACHE_TCP_CONNECTIONS)
  {	
   LimeServerID l = new LimeServerID(s.getInetAddress(), s.getPort());
   Connection c = new Connection(s);
   cached.put(l, c);
  }
 }
 
 /**
  * This method must be invoked when an accepted connection is closed.
  *
  * param s "accepted" socket to close
  * @throws IOException
  */
 synchronized static void remove(Socket s) throws IOException
 {
  if (LimeConstants.CACHE_TCP_CONNECTIONS)
  {	
   LimeServerID l = new LimeServerID(s.getInetAddress(), s.getPort());
   Connection c = (Connection) cached.remove(l);
   if (c != null) c.out.close();
  }
 }
 
 /**
  * Constructor method (private). Invoked only within method "get".
  * 
  * @param target LimeServer to connect to
  * @throws IOException
  */
 private Connection(LimeServerID target) throws IOException
 {
  socket = new Socket(target.getHost(), target.getPort());
  if (LimeConstants.CACHE_TCP_CONNECTIONS && LimeConstants.KEEP_ALIVE) socket.setKeepAlive(true);
  out = new ObjectOutputStream(socket.getOutputStream());
  out.flush(); // .. flushes "magic number": prevents deadlock 
  error = 0;
  if (LimeConstants.CACHE_TCP_CONNECTIONS) new UnicastServant(socket).start();
 }
  
 /**
  * Constructor method (private). Invoked only within method "add".
  * 
  * @param s "accepted" socket to use
  * @throws IOException
  */
 private Connection(Socket s) throws IOException
 {
  socket = s;
  if (LimeConstants.KEEP_ALIVE) socket.setKeepAlive(true);
  out = new ObjectOutputStream(s.getOutputStream());
  out.flush(); // .. flushes "magic number": prevents deadlock
  error = 0;
 }
 
 /**
  * Sends an object using this connection. 
  * 
  * @param o object to send
  * @throws IOException
  */
 synchronized void send(Object o) throws IOException
 {
  try 
  {
   out.reset(); // resets strem (.. must be done for cached connections !)
   out.writeObject(o);  
   out.flush();
  }
  catch (IOException ioe) { error++; throw ioe; }
 }
 
 /**
  * This method must be invoked when we are finished with using this connection (it doesn't need to be synchronized
  * since it affects only connections that are not cached = shared).
  * 
  * @throws IOException
  */
 void finished() throws IOException
 {
  if (!LimeConstants.CACHE_TCP_CONNECTIONS)
  {
   out.close();
   socket.close();
  }	
 }
 
 // is this connection still alive ?
 private boolean mustReconnect()
 {
  return error > 0;	
 }
  
}
